﻿using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;

namespace Postnuclear
  {
  /// <summary>
  /// Smartly subscribes to observed event when there is at least one listener.
  /// Un-subscribes from observed event when there is no listeners.
  /// Predicated handlers are not taken into account.
  /// </summary>
  /// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
  /// <remarks>
  /// This class allows to unsubscribe from target objects when it is not listened anymore.
  /// It helps to avoid memory leaks in such situation.
  /// </remarks>
  internal sealed class EventObserver<TEventArgs>
    where TEventArgs : EventArgs
    {
    #region Public

    // Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="EventObserver{TEventArgs}"/> class.
    /// </summary>
    /// <param name="addHandler">The handler which subscribes specified event handler to observed instance.</param>
    /// <param name="removeHandler">The which un-subscribes specified event handler to observed instance.</param>
    /// <param name="handlers">The predicated handlers.</param>
    public EventObserver(
      Action<EventHandler<TEventArgs>> addHandler,
      Action<EventHandler<TEventArgs>> removeHandler,
      params EventHandler<TEventArgs>[] handlers)
      {
      Contract.Requires<ArgumentNullException>(addHandler != null);
      Contract.Requires<ArgumentNullException>(removeHandler != null);
      Contract.Requires<ArgumentNullException>(handlers != null);

      AddHandler = addHandler;
      RemoveHandler = removeHandler;
      Handlers = handlers;
      }

    // Events

    /// <summary>
    /// Occurs when observed event is raised.
    /// </summary>
    public event EventHandler<TEventArgs> Event
      {
      add
        {
        EventHandler += value;
        IsActive = EventHandler != null;
        }

      remove
        {
        EventHandler -= value;
        IsActive = EventHandler != null;
        }
      }

    // Properties

    /// <summary>
    /// Gets a value indicating whether there are subscribers to this instance.
    /// </summary>
    /// <value>
    /// <c>true</c> if there are some subscribers; otherwise, <c>false</c>.
    /// </value>
    public bool IsActive
      {
      get
        {
        return _isActive;
        }

      private set
        {
        if (value == _isActive)
          {
          return;
          }

        _isActive = value;

        if (IsActive)
          {
          AddHandler(OnValueChanged);
          }
        else
          {
          RemoveHandler(OnValueChanged);
          }
        }
      }

    #endregion

    #region Private

    // Properties

    private EventHandler<TEventArgs> EventHandler
      {
      get;
      set;
      }

    // Methods

    [DebuggerHidden]
    private void OnValueChanged(object sender, TEventArgs e)
      {
      Handlers.ForEach(handler => handler(sender, e));
      EventHandler.Raise(sender, e);
      }

    // Fields

    private readonly Action<EventHandler<TEventArgs>> AddHandler;
    private readonly Action<EventHandler<TEventArgs>> RemoveHandler;
    private readonly EventHandler<TEventArgs>[] Handlers;

    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private bool _isActive;

    #endregion
    }
  }